﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Assertions;

namespace Nirvana
{
	/// <summary>
	/// 简单阴影，用于设备切换性能设置,减少阴影mesh的DC
	/// </summary>
	[AddComponentMenu("Nirvana/Render/SimpleShadow")]
	public sealed class SimpleShadow : MonoBehaviour
	{

		private static RaycastHit[] raycastHits = new RaycastHit[8];
		private static Plane[] planes = new Plane[6];

		[Tooltip("The material for this shadow.")]
		[SerializeField]
		private Material shadowMaterial;

		[SerializeField]
		[Tooltip("Get the shadow size.")]
		private Vector2 shadowSize = new Vector2(0.5f, 0.5f);

		[SerializeField]
		[Tooltip("The raycast mask.")]
		[LayerMask]
		private int groundMask = -1;

		[SerializeField]
		[Tooltip("The offset for height.")]
		private float offset = 0.01f;

		[Range(0.1f, 5f)]
		[SerializeField]
		[Tooltip("The distance scale.")]
		private float scaleDistance = 1f;

		[SerializeField]
		[Tooltip("静态物体勾选，用于性能优化.")]
		private bool staticEnvironment;

		[SerializeField]
		[Tooltip("动态物体勾选，需要render关闭投射阴影.")]
		private bool checkRealtimeShadow = true;
		[SerializeField]
		[Tooltip("用于动态物体阴影检测.")]
		private Renderer relativeRenderer;

		private ShadowMeshManager.SimpleShadowMeshStruct shadowstruct;
		private Vector3 startPos, endPos;
		private Quaternion quaternion;
		private Transform mTransform;
		private bool isNeedShow;
		public bool IsNeedShow { get { return isNeedShow; } set { isNeedShow = value; } }
		private bool inCameraViewBound;
		public bool InCameraViewBound { get { return inCameraViewBound; } set { inCameraViewBound = value; } }


		public void GenterVector(Vector3[] vectors,Vector2[] uvs,int startIndex)
        {
			float offY = Mathf.Abs(transform.position.y - this.startPos.y);
			Vector3 forward = transform.forward;
			Vector3 right = transform.right;
			float scale = Mathf.Min(1f, 1f / (1f + this.scaleDistance * offY));
			Vector2 curScale = this.shadowSize * scale;
			int index = 4 * startIndex;
			vectors[index] = this.startPos + forward * curScale.y * 0.5f - right * curScale.x*0.5f;
			vectors[index + 1] = this.startPos + forward * curScale.y * 0.5f + right * curScale.x * 0.5f;
			vectors[index + 2] = this.startPos - forward * curScale.y * 0.5f - right * curScale.x * 0.5f;
			vectors[index + 3] = this.startPos - forward * curScale.y * 0.5f + right * curScale.x * 0.5f;
			uvs[index] = new Vector2(0, 0);
			uvs[index+1] = new Vector2(1, 0);
			uvs[index+2] = new Vector2(0, 1);
			uvs[index+3] = new Vector2(1, 1);
		}

		public Material ShadowMaterial
        {
            get { return this.shadowMaterial; }
            set
            {
				if(this.shadowMaterial != value)
                {
					this.shadowMaterial = value;
					this.Remove();
					this.Add();
                }
            }
        }

		public Vector2 ShadowSize
        {
            get { return this.shadowSize; }
            set { this.shadowSize = value; }
        }

		public int GroundMask
        {
            get { return this.groundMask; }
            set { this.groundMask = value; }
        }

		public float Offset
        {
            get { return this.offset; }
            set { this.offset = value; }
        }

		public float ScaleDistance
        {
            get { return this.scaleDistance; }
            set { this.scaleDistance = value; }
        }

		void Awake()
        {
			mTransform = transform;

		}

		void OnEnable()
        {
			this.Remove();
			this.Add();
        }

		void OnDisable()
        {
			this.Remove();
        }

#if UNITY_EDITOR
		void OnValidate()
        {
			mTransform = transform;
			if (Application.isPlaying)
            {
				this.Remove();
				this.Add();
				this.isNeedShow = true;
			}
        }
#endif

		void Update()
        {
			Camera camera = Camera.main;
			if (camera == null || !camera.isActiveAndEnabled) return;
            if (this.checkRealtimeShadow)
            {
				bool noDisable = QualitySettings.shadows != ShadowQuality.Disable;
				if(noDisable && this.relativeRenderer != null)
                {
					var shadowcastingMode = this.relativeRenderer.shadowCastingMode;
					noDisable = shadowcastingMode != UnityEngine.Rendering.ShadowCastingMode.Off;
                }
                if (noDisable)
                {
					this.Remove();
					return;
                }

				if (!this.shadowstruct.IsHaveShadow()) this.Add();

            }
			bool needupdate = false;
			if (this.endPos != mTransform.position || this.quaternion != mTransform.rotation)
            {
				needupdate = true;
				this.IsNeedShow = true;
				this.endPos = mTransform.position;
				this.quaternion = mTransform.rotation;
            }

            if (this.staticEnvironment)
            {
				if (needupdate) this.CheckCulling(mTransform.position, camera);
            }
            else
            {
				this.CheckCulling(mTransform.position, camera);
			}


        }


		private void Add()
        {
			Assert.IsFalse(this.shadowstruct.IsHaveShadow());
			if(this.shadowMaterial != null)
            {
				this.shadowstruct = ShadowMeshManager.Instance.CreateShadowMesh(this);
            }

        }

		private void Remove()
        {
            if (this.shadowstruct.IsHaveShadow())
            {
				Singleton<ShadowMeshManager>.Instance.Remove(this.shadowstruct);
				this.shadowstruct.Clear();
            }
        }

		private void CheckCulling(Vector3 startPos,Camera camera)
        {
			Ray ray = new Ray(startPos + 10000f * Vector3.up, Vector3.down);
			int count = Physics.RaycastNonAlloc(ray, raycastHits, float.PositiveInfinity, this.groundMask);
			if(count >0 && count <= raycastHits.Length)
            {
				//查找地面
				float maxY = float.NegativeInfinity;
				for(int i = 0; i < count; i++)
                {
					RaycastHit hit = raycastHits[i];
					if (maxY < hit.point.y) maxY = hit.point.y;
                }

				Vector3 pos = new Vector3(startPos.x, maxY + this.offset, startPos.z);
				if (this.startPos != pos)
                {
					this.startPos = pos;
					IsNeedShow = true;

				}

				//计算bounds是否在camera的视景范围内，不是则剔除
				GeometryUtilityHelper.ExtractPlanes(planes, camera);
				Vector3 size = new Vector3(this.shadowSize.x, 0.1f, this.shadowSize.y);
				Bounds bounds = new Bounds(this.startPos, size);
				bool flag = GeometryUtility.TestPlanesAABB(planes, bounds);
				if (this.InCameraViewBound != flag)
				{
					this.InCameraViewBound = (flag);
					this.IsNeedShow = (true);
				}

			}
            else
            {
				this.Remove();
            }

        }





	}
}

